#ifndef PCPS_DISK_IO_HEADER
#define PCPS_DISK_IO_HEADER

#include <queue>

#include <src/exception.h>

namespace pcps {

//--------------------------------------------------------------------------------
// pcps::disk_io_detail -- function used by the disk io thread
//--------------------------------------------------------------------------------

void * disk_io_detail(void * arg);

//--------------------------------------------------------------------------------
// pcps::Disk_IO_Manager -- Class to manage input/output to the disk in a separate
//                          thread to allow for disk communication hiding
//--------------------------------------------------------------------------------

class Disk_IO_Manager {

  private:

    ////////////////////////////////////////////////////////////////////////////
    // data members
    ////////////////////////////////////////////////////////////////////////////
    const bool _use_disk;                        // whether to write data do disk or to memory
    pthread_t _thread;                           // thread that will perform disk io
    pthread_mutex_t _cond_mutex;                 // mutex used by the disk io thread
    pthread_cond_t _cond;                        // condition variable used by the disk io thread
    bool _terminate;                             // flag used to terminate the disk io thread
    std::queue<char             > _type_queue;   // the job type for each task, either 'r' for read or 'w' for write
    std::queue<size_t           > _size_queue;   // the number of bytes of data for each task
    std::queue<char *           > _data_queue;   // pointers to the data that should be read from or written to disk
    std::queue<bool *           > _flag_queue;   // pointers to flags telling whether the task is complete
    std::queue<pthread_mutex_t *> _mutx_queue;   // pointers to mutexes used by other threads to wait for the tasks to complete
    std::queue<pthread_cond_t * > _cond_queue;   // pointers to condition variables used for signalling that the tasks are complete
    std::queue<std::string *    > _name_queue;   // pointers to strings containing the file names to read from or write to
    std::map<std::string, size_t> _virtual_disk_sizes; // sizes of the data blocks that make up the virtual disk
    std::map<std::string, boost::shared_array<char> > _virtual_disk; // map from file names to data arrays in memory.
                                                                     // used when disk storage is not requested

  public:

    ////////////////////////////////////////////////////////////////////////////
    // default constructor
    ////////////////////////////////////////////////////////////////////////////
    Disk_IO_Manager(const bool use_disk) 
      : _use_disk(use_disk),
        _terminate(false)
    {

      // initialize mutex and condition variables
      pthread_mutex_init(&_cond_mutex, NULL);
      pthread_cond_init(&_cond, NULL);

      // create thread attribute object to ensure the thread is joinable
      pthread_attr_t attr;
      pthread_attr_init(&attr);
      pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

      // start the thread that will access the disk
      const int rc = pthread_create(&_thread, &attr, pcps::disk_io_detail, (void *)this);
      if (rc != 0)
        throw pcps::Exception( (boost::format("pthread_create failed in pcps::Disk_IO_Manager constructor with return code %i")
                                % rc).str() );

      // destroy thread attribute object
      pthread_attr_destroy(&attr);

    }

    ////////////////////////////////////////////////////////////////////////////
    // destructor
    ////////////////////////////////////////////////////////////////////////////
    ~Disk_IO_Manager() {

      // instruct the disk io thread to terminate
      pthread_mutex_lock(&_cond_mutex);
      _terminate = true;
      pthread_cond_signal(&_cond);
      pthread_mutex_unlock(&_cond_mutex);

      // wait for thread to terminate
      void * status;
      const int rc = pthread_join(_thread, &status);
      if (rc != 0)
        throw pcps::Exception( (boost::format("pthread_join failed in pcps::Disk_IO_Manager destructor with return code %i.")
                                 % rc).str() );

      // ensure that queues are empty
      if (!_type_queue.empty()) throw pcps::Exception("_type_queue is not empty in pcps::Disk_IO_Manager destructor");
      if (!_size_queue.empty()) throw pcps::Exception("_size_queue is not empty in pcps::Disk_IO_Manager destructor");
      if (!_data_queue.empty()) throw pcps::Exception("_data_queue is not empty in pcps::Disk_IO_Manager destructor");
      if (!_flag_queue.empty()) throw pcps::Exception("_flag_queue is not empty in pcps::Disk_IO_Manager destructor");
      if (!_mutx_queue.empty()) throw pcps::Exception("_mutx_queue is not empty in pcps::Disk_IO_Manager destructor");
      if (!_cond_queue.empty()) throw pcps::Exception("_cond_queue is not empty in pcps::Disk_IO_Manager destructor");
      if (!_name_queue.empty()) throw pcps::Exception("_name_queue is not empty in pcps::Disk_IO_Manager destructor");

      // destroy mutex and condition variables
      pthread_cond_destroy(&_cond);
      pthread_mutex_destroy(&_cond_mutex);

    }

    ////////////////////////////////////////////////////////////////////////////
    // function to return a pointer to the manager's mutex
    ////////////////////////////////////////////////////////////////////////////
    pthread_mutex_t * get_mutex() { return &_cond_mutex; }

    ////////////////////////////////////////////////////////////////////////////
    // function to return a pointer to the manager's condition variable
    ////////////////////////////////////////////////////////////////////////////
    pthread_cond_t * get_condition() { return &_cond; }

    ////////////////////////////////////////////////////////////////////////////
    // function to schedule a disk io task
    ////////////////////////////////////////////////////////////////////////////
    void schedule_task(char type, size_t n, char * data, bool * flag, pthread_mutex_t * m, pthread_cond_t * c, std::string * fname) {

      // check that the job type is acceptable
      if (type != 'r' && type != 'w')
        throw pcps::Exception( (boost::format("pcps::Disk_IO_Manager::schedule_task requires type to be 'r' or 'w', you requested '%s'")
                                % type).str() );

      // mark the job as incomplete
      *flag = false;

      // lock the manager's mutex to prevent interfering with the disk io thread
      pthread_mutex_lock(&_cond_mutex);

      // add the task to the queues
      _type_queue.push(type);
      _size_queue.push(n);
      _data_queue.push(data);
      _flag_queue.push(flag);
      _mutx_queue.push(m);
      _cond_queue.push(c);
      _name_queue.push(fname);

      // signal the disk io thread to let it know that there is work to be done
      pthread_cond_signal(&_cond);

      // unlock the manager's mutex
      pthread_mutex_unlock(&_cond_mutex);

    }

    ////////////////////////////////////////////////////////////////////////////
    // allow the thread function to access this class's private data
    ////////////////////////////////////////////////////////////////////////////
    friend void * pcps::disk_io_detail(void *);

};

} // end namespace pcps

#endif
